/*
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *
 *   版权所有  2014-2015 成都星锐蓝海网络科技有限公司
 *   商业许可请联系  +86-18682011860    QQ:66442834
 *   
 */

#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <netdb.h>  //  for getaddrinfo
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/select.h>
#include "xyz_log.h"
#include "udphelp.h"

static int udpSetNonblock(int fd)
{
    
	int flags = 0;
	if (fd < 0) {
		xyz_log_error("argument is error");
		return -1;
	}
	if ((flags = fcntl(fd, F_GETFL, 0)) == -1) {
		xyz_log_error("fcntl : %s", strerror(errno));
		return -1;
	} else if (fcntl(fd, F_SETFL, flags | O_NONBLOCK | O_ASYNC) != 0) {
		xyz_log_error("fcntl : %s", strerror(errno));
		return -1;
	}
     
	return 0;
}

int udpNewConnect(int nonblock, long msto)
{
	int fd = -1;
	struct sockaddr_in sin;

	fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (fd < 0) {
		xyz_log_error("socket : %s", strerror(errno));
		return -1;
	}
	if (1 == nonblock) {
		if (udpSetNonblock(fd) < 0) {
			close(fd);
			return -1;
		}
	} else {
		if (msto < 1) {
			xyz_log_error("timeout value error");
			close(fd);
			return -1;
		}
		struct timeval tv;
		tv.tv_sec = msto / 1000;
		tv.tv_usec = (msto % 1000) * 1000;
		if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
			xyz_log_error("setsockopt : %s", strerror(errno));
			close(fd);
			return -1;
		}
	}
	memset(&sin, 0, sizeof sin);
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = INADDR_ANY;
	sin.sin_port = htons(0);
	if (bind(fd, (const struct sockaddr *)&(sin), sizeof(sin)) == -1) {
		xyz_log_error("bind : %s", strerror(errno));
		close(fd);
		return -1;
	}
	return fd;
}

int udpNewLongConnect(const char *sip,
					  const char *sport,
					  const char *dip,
					  const char *dport,
					  int nonblock,
					  unsigned long msto)
{
	int fd = -1;
	int yes = 1;
	struct sockaddr_in src;
	struct sockaddr_in dst;
	struct addrinfo hints;
	struct addrinfo *res = NULL;
	char myport[8] = { 0 };

	if (!dip || !dport) {
		xyz_log_error("arguments is error");
		return -1;
	}
	memset(&dst, 0, sizeof(dst));
	dst.sin_family = AF_INET;
	dst.sin_addr.s_addr = inet_addr(dip);
	dst.sin_port = htons(atol(dport));
	if (dst.sin_port > 0xffff) {
		xyz_log_error("port \"%s\" is invalid", dport);
		return -1;
	}
	fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (fd < 0) {
		xyz_log_error("socket : %s", strerror(errno));
		return -1;
	}
	if (1 == nonblock) {
		if (udpSetNonblock(fd) < 0) {
			close(fd);
			return -1;
		}
	} else {
		if (msto < 1) {
			xyz_log_error("timeout value error");
			close(fd);
			return -1;
		}
		struct timeval tv;
		tv.tv_sec = msto / 1000;
		tv.tv_usec = (msto % 1000) * 1000;
		if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
			xyz_log_error("setsockopt : %s", strerror(errno));
			close(fd);
			return -1;
		}
	}
	if (!sip && !sport) {
		memset(&hints, 0, sizeof (struct addrinfo));
		hints.ai_family = AF_INET;
		hints.ai_socktype = SOCK_STREAM;
		hints.ai_flags = AI_PASSIVE;
		getaddrinfo(NULL, myport, &hints, &res);
		if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes))) {
			xyz_log_error("setsockopt : %s", strerror(errno));
			close(fd);
			return -1;
		}
		if (bind(fd, res->ai_addr, res->ai_addrlen) < 0) {
			xyz_log_error("bind : %s", strerror(errno));
			close(fd);
			return -1;
		}
	} else {
		memset(&src, 0, sizeof(src));
		src.sin_family = AF_INET;
		if (!sip) {
			src.sin_addr.s_addr = INADDR_ANY;
		} else {
			src.sin_addr.s_addr = inet_addr(sip);
		}
		src.sin_port = htons(atol(sport));
		if (src.sin_port > 0xffff) {
			xyz_log_error("remote port \"%s\" is invalid", sport);
			close(fd);
			return -1;
		}
		if (bind(fd, (struct sockaddr *)&src, sizeof(src)) < 0) {
			xyz_log_error("bind : %s", strerror(errno));
			close(fd);
			return -1;
		}
	}
#if 0
	if (connect(fd, (struct sockaddr *)&dst, sizeof(dst))) {
		xyz_log_error("connect : %s", strerror(errno));
		close(fd);
		return -1;
	}
#endif
	return fd;
}

int udpNewServer(const char *ip,
		  	     uint16_t port,
		  	     int nonblock,
		  	     long msto)
{
	int fd = -1;
	struct sockaddr_in addr;

	if(port < 1){
		xyz_log_error("Args error");
		return -1;
	}
	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	if (ip) {
		addr.sin_addr.s_addr = inet_addr(ip);
	} else {
		addr.sin_addr.s_addr = htonl(INADDR_ANY);
	}
	addr.sin_port = htons(port);

	fd = socket(AF_INET, SOCK_DGRAM, 0);
	if(fd < 0){
		xyz_log_error("socket : %s", strerror(errno));
		return (-1);
	}
	if (1 == nonblock) {
		if (udpSetNonblock(fd) < 0) {
			xyz_log_error("Set socket no block error");
			close(fd);
			return -1;
		}
	} else {
		if (msto < 1) {
			xyz_log_error("timeout value error");
			close(fd);
			return -1;
		}
		struct timeval tv;
		tv.tv_sec = msto / 1000;
		tv.tv_usec = (msto % 1000) * 1000;
		if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
			xyz_log_error("setsockopt : %s", strerror(errno));
			close(fd);
			return -1;
		}
	}
	if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0){
		xyz_log_error("bind : %s", strerror(errno));
		close(fd);
		return (-1);
	}
	return fd;
}

int udpTimeRecv(int fd, void *buff, int size, struct sockaddr_in *from, int msec)
{
	int rc = 0;
	struct timeval tv;
	fd_set fset;
	int addrlen = sizeof(struct sockaddr_in);

	if (fd < 1 || !buff || !from) {
		xyz_log_error("Arguments is error");
		return -1;
	}
	tv.tv_sec = msec / 1000;
	tv.tv_usec = ((msec % 1000) * 1000);
	FD_ZERO(&fset);
	FD_SET(fd, &fset);
	rc = select(fd + 1, &fset, NULL, NULL, &tv);
	if (rc > 0) {
		if (FD_ISSET(fd, &fset)) {
			rc = recvfrom(fd, buff, size, 0, (struct sockaddr *)from, &addrlen);
			if (rc > 0) {
				return rc;
			} else {
				xyz_log_error("recvfrom : %s", strerror(errno));
				return -1;
			}
		} else {
			xyz_log_error("select return not is FD %d", fd);
			return -1;
		}
	} else if (0 == rc) {
		xyz_log_debug("Select Time Expired");
		return 0;
	} else {
		if (errno == EINTR) {
			xyz_log_error("Select Interrupted by signal");
			return -1;
		}
	}
	return -1;
}
